package dev.funkymuse.bytearray


/**
 * Helper class to assist with building byte arrays. Works similarly to
 * [StringBuilder].
 */
class ByteArrayBuilder(initialCap: Int = 128) {

    private var buffer = ByteArray(maxOf(initialCap, 128))
    private var pos: Int = 0

    /**
     * Returns current size of the buffer.
     */
    val size: Int
        get() = pos

    /**
     * Appends a single [byte] to the buffer.
     *
     * @param byte Value to append to buffer.
     */
    @Synchronized
    fun append(byte: Byte) {
        append(byteArrayOf(byte), 0..0)
    }

    /**
     * Appends a byte [array] to the buffer, optionally by only copying
     * a section of it as specified by [srcRange].
     *
     * @param array [ByteArray] (or part thereof) to append.
     * @param srcRange (optional) Section of the array to append.
     */
    @Synchronized
    fun append(array: ByteArray, srcRange: IntRange = array.indices) {
        val srcCount = maxOf(srcRange.last - srcRange.first + 1, 0)
        if (srcCount < 1) return

        val indices = array.indices
        when {
            srcRange.first !in indices ->
                throw ArrayIndexOutOfBoundsException("Range start outside array bounds")
            srcRange.last !in indices ->
                throw ArrayIndexOutOfBoundsException("Range end outside array bounds")
        }

        val newCount = pos + srcCount
        if (newCount > buffer.size) {
            val newBuff = ByteArray(maxOf(newCount, buffer.size * 2 + 2))
            System.arraycopy(buffer, 0, newBuff, 0, pos)
            buffer = newBuff
        }

        System.arraycopy(array, srcRange.first, buffer, pos, srcCount)
        pos += srcCount
    }

    /**
     * Copies the contents of the buffer to a new [ByteArray] and returns
     * the new array.
     *
     * @return New [ByteArray] instance containing contents of the buffer.
     */
    @Synchronized
    fun build(): ByteArray = ByteArray(pos).apply {
        System.arraycopy(buffer, 0, this, 0, pos)
    }
}

/**
 * Creates a new [ByteArrayBuilder] instance, calls [block] with the
 * builder as receiver, finally returning the [ByteArray] generated by call
 * to [ByteArrayBuilder.build].
 *
 * Example:
 *
 * ```
 * val encryptedString = buildByteArray {
 *     append("Salted__".toByteArray())
 *     append(salt)
 *     append(cipher.doFinal(message.toByteArray()))
 * }.toBase64String()
 * ```
 */
inline fun buildByteArray(block: ByteArrayBuilder.() -> Unit): ByteArray =
        ByteArrayBuilder().apply { block(this) }.build()